package cn.bearspark.happening.provider.dao.buffer;

import cn.bearspark.framework.redis.starter.buffer.RedisBuffer;
import cn.bearspark.happening.enumeration.vote.AnonymousEnum;
import cn.bearspark.happening.provider.dao.key.VoteBufferKeyBuilder;
import cn.bearspark.happening.provider.dao.mapper.VoteUserMapper;
import cn.bearspark.happening.provider.dao.po.VoteUserPO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 投票 id 和 用户 id 的缓存器
 *
 * @author f
 */
@Component
public class VoteUserIdSetBuffer extends RedisBuffer {

    /**
     * 用户投票 id 缓存的过期时间，默认为 5min
     */
    private static final int EXPIRE = 60 * 5;

    private final VoteUserMapper voteUserMapper;

    private final VoteBufferKeyBuilder voteBufferKeyBuilder;

    public VoteUserIdSetBuffer(RedisTemplate<String, Object> redisTemplate,
                               VoteUserMapper voteUserMapper,
                               VoteBufferKeyBuilder voteBufferKeyBuilder) {
        super(redisTemplate);
        this.voteUserMapper = voteUserMapper;
        this.voteBufferKeyBuilder = voteBufferKeyBuilder;
    }

    /**
     * 获取指定用户在指定投票中投票的选项
     *
     * @param voteId 指定的投票 id
     * @param userId 指定的用户 id
     * @return 指定用户在指定投票中投票的选项 id 集合
     */
    public Set<Integer> getVoteOptionIdSet(long voteId, long userId) {
        // 在 Redis 中查询用户投票的缓存
        String key = voteBufferKeyBuilder.buildVoteUserIdKey(voteId, userId, AnonymousEnum.ANONYMITY.getCode());
        Set<Integer> optionIdSet = Objects.requireNonNull(redisTemplate.opsForSet().members(key)).stream()
                .map(super::objToInteger).collect(Collectors.toSet());

        // 如果选项 id 的集合不为空，则直接返回
        if (!optionIdSet.isEmpty()) {
            return optionIdSet;
        }

        // 在数据库中查询用户投票的数据，将其缓存起来，并返回
        optionIdSet = voteUserMapper.getOptionIdSet(voteId, userId);
        if (!optionIdSet.isEmpty()) {
            redisTemplate.opsForSet().add(key, optionIdSet.toArray());
            redisTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
        }

        return optionIdSet;
    }

    /**
     * 获取指定投票中指定用户 非匿名投票的 选中的选项 id 的集合
     *
     * @param voteId     指定的投票 id
     * @param userIdList 指定的用户 id 集合
     * @return 指定投票中指定用户 非匿名投票的 选中的选项 id 的映射，key：用户 id，value：选项的 id 集合
     */
    public Map<Long, Set<Integer>> mapVoteOptionIdSet(long voteId, List<Long> userIdList) {
        List<Long> missingUserIdList = new ArrayList<>();
        Map<Long, Set<Integer>> userOptionIdMap = new HashMap<>(userIdList.size());
        super.executePipelined(operations -> userIdList.forEach(userId -> {
            // 在 Redis 中查询用户投票的缓存
            Set<Integer> optionIdSet = Objects.requireNonNull(redisTemplate.opsForSet().members(
                    voteBufferKeyBuilder.buildVoteUserIdKey(voteId, userId, AnonymousEnum.PUBLIC.getCode()))
            ).stream().map(super::objToInteger).collect(Collectors.toSet());

            if (optionIdSet.isEmpty()) {
                // 如果选项 id 的集合为空，则将其加入缺失的用户 id 集合
                missingUserIdList.add(userId);
            } else {
                // 否则将其放入映射中
                userOptionIdMap.put(userId, optionIdSet);
            }
        }));

        // 如果没有缺失的用户 id，则直接返回
        if (missingUserIdList.isEmpty()) {
            return userOptionIdMap;
        }

        // 在数据库中查询用户投票的数据，将其缓存起来，并返回
        Map<Long, Set<Integer>> selectedUserOptionIdMap =
                voteUserMapper.listSelectedOption(voteId, missingUserIdList, AnonymousEnum.PUBLIC.getCode())
                        .stream().collect(Collectors.groupingBy(
                                VoteUserPO::getVoterId,
                                Collectors.mapping(VoteUserPO::getOptionId, Collectors.toSet())
                        ));
        if (!selectedUserOptionIdMap.isEmpty()) {
            selectedUserOptionIdMap.forEach((userId, optionIdSet) -> {
                String key = voteBufferKeyBuilder.buildVoteUserIdKey(voteId, userId, AnonymousEnum.PUBLIC.getCode());
                redisTemplate.opsForSet().add(key, optionIdSet.toArray());
                redisTemplate.expire(key, EXPIRE, TimeUnit.SECONDS);
            });

            userOptionIdMap.putAll(selectedUserOptionIdMap);
        }

        return userOptionIdMap;
    }

    /**
     * 清理指定投票的所有用户选中选项 id 的缓存
     *
     * @param voteId 指定的投票 id
     */
    public void clean(long voteId) {
        super.cleanWithPattern(voteBufferKeyBuilder.buildVoteUserIdKeyPattern(voteId));
    }
}
